package cn.felord.security.autoconfigure.jwt;

import cn.felord.security.autoconfigure.TokenGenerator;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;

import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Optional;

/**
 * The type Jwt configuration.
 *
 * @author felord.cn
 * @since 2021 /8/6 18:28
 */
@EnableConfigurationProperties({JwtProperties.class})
@Configuration(
        proxyBeanMethods = false
)
@AllArgsConstructor
public class JwtConfiguration {

    private final JwtProperties jwtProperties;
    private ResourceLoader resourceLoader;


    /**
     * Jwk source jwk source.
     *
     * @return the jwk source
     * @throws IOException              the io exception
     * @throws KeyStoreException        the key store exception
     * @throws JOSEException            the jose exception
     * @throws CertificateException     the certificate exception
     * @throws NoSuchAlgorithmException the no such algorithm exception
     */
    @Bean
    @ConditionalOnMissingBean
    public JWKSource<SecurityContext> jwkSource() throws IOException, KeyStoreException, JOSEException, CertificateException, NoSuchAlgorithmException {
        String absolutePathLocation = jwtProperties.getAbsolutePathLocation();
        Resource resource = absolutePathLocation != null ? new FileSystemResource(absolutePathLocation) :
                resourceLoader.getResource(jwtProperties.getClassPathLocation());
        char[] pin = jwtProperties.getKeyPassword().toCharArray();
        KeyStore pkcs12 = KeyStore.getInstance("PKCS12");
        pkcs12.load(resource.getInputStream(), pin);
        RSAKey rsaKey = RSAKey.load(pkcs12, jwtProperties.getAlias(), pin);
        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }

    /**
     * Jwt decoder jwt decoder.
     *
     * @param jwkSource the jwk source
     * @return the jwt decoder
     */
    @Bean
    @ConditionalOnClass(
            name = {"org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer"}
    )
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        ConfigurableJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
        JWSVerificationKeySelector<SecurityContext> jwsKeySelector = new JWSVerificationKeySelector<>(JWSAlgorithm.RS256, jwkSource);
        jwtProcessor.setJWSKeySelector(jwsKeySelector);
        jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> {
        });
        return new NimbusJwtDecoder(jwtProcessor);
    }

    /**
     * Jwt encoder jwt encoder.
     *
     * @param jwkSource the jwk source
     * @return the jwt encoder
     */
    @Bean
    @ConditionalOnMissingBean
    public JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
        return new NimbusJwtEncoder(jwkSource);
    }

    /**
     * Token generator token generator.
     *
     * @param jwtEncoder the jwt encoder
     * @param provider   只能指定一个
     * @return the token generator
     */
    @Bean
    TokenGenerator<?> tokenGenerator(JwtEncoder jwtEncoder, Optional<JwtClaimsConsumer> provider) {
        return provider.map(jwtClaimsConsumer -> new JwtTokenGenerator(jwtEncoder, jwtProperties, jwtClaimsConsumer))
                .orElseGet(() -> new JwtTokenGenerator(jwtEncoder, jwtProperties));
    }
}
